package vip.liux.backend.application.serviceImpl.pay;


import jakarta.annotation.Resource;
import jakarta.validation.Validator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import vip.liux.application.dots.ListResultDto;
import vip.liux.backend.application.service.pay.AutoMapperProfile;
import vip.liux.backend.application.service.pay.PayChannelService;
import vip.liux.backend.application.service.pay.dto.channel.PayChannelCreateDto;
import vip.liux.backend.application.service.pay.dto.channel.PayChannelDto;
import vip.liux.backend.application.service.pay.dto.channel.PayChannelUpdateDto;
import vip.liux.contracts.infrastructure.pay.PayClient;
import vip.liux.contracts.infrastructure.pay.PayClientConfig;
import vip.liux.contracts.infrastructure.pay.PayClientFactory;
import vip.liux.contracts.infrastructure.pay.impl.NonePayClientConfig;
import vip.liux.contracts.infrastructure.pay.impl.alipay.AlipayPayClientConfig;
import vip.liux.contracts.infrastructure.pay.impl.weixin.WxPayClientConfig;
import vip.liux.contracts.models.pay.PayChannel;
import vip.liux.contracts.repositories.pay.PayChannelRepository;
import vip.liux.contracts.shared.enums.Status;
import vip.liux.contracts.shared.enums.pay.PayChannelCode;

import java.util.Collection;
import java.util.Optional;

import static vip.liux.contracts.shared.consts.GeneralConstants.mapToBean;
import static vip.liux.contracts.shared.enums.ErrorCode.*;
import static vip.liux.contracts.shared.util.ServiceExceptionUtil.exception;


/**
 * 支付渠道 Service 实现类
 *
 * @author aquan
 */
@Service
@Slf4j
@Validated
public class PayChannelServiceImpl implements PayChannelService {

    @Resource
    private PayClientFactory payClientFactory;

    @Resource
    private PayChannelRepository payChannelMapper;

    @Resource
    private Validator validator;

    @Override
    public PayChannelDto createChannel(PayChannelCreateDto reqDto) {
        // 断言是否有重复的
        PayChannelDto dbChannel = getChannelByAppIdAndCode(reqDto.getAppId(), reqDto.getCode());
        if (dbChannel != null) {
            throw exception(CHANNEL_EXIST_SAME_CHANNEL_ERROR);
        }

        // 新增渠道
        PayChannel channel = AutoMapperProfile.INSTANCE.convert(reqDto);
        channel.setConfig(parseConfig(reqDto.getCode(), reqDto.getConfig()));
        payChannelMapper.save(channel);
        return AutoMapperProfile.INSTANCE.convert(channel);
    }

    @Override
    public PayChannelDto updateChannel(Long id, PayChannelUpdateDto updateDto) {
        // 校验存在
        PayChannel dbChannel = validateChannelExists(id);

        // 更新
        AutoMapperProfile.INSTANCE.convert(updateDto, dbChannel);

        dbChannel.setConfig(parseConfig(dbChannel.getCode(), updateDto.getConfig()));
        payChannelMapper.save(dbChannel);
        return AutoMapperProfile.INSTANCE.convert(dbChannel);
    }

    /**
     * 解析并校验配置
     *
     * @param code      渠道编码
     * @param configStr 配置
     * @return 支付配置
     */
    private PayClientConfig parseConfig(String code, String configStr) {
        // 解析配置
        Class<? extends PayClientConfig> payClass = PayChannelCode.isAlipay(code) ? AlipayPayClientConfig.class
                : PayChannelCode.isWeixin(code) ? WxPayClientConfig.class
                : NonePayClientConfig.class;

        PayClientConfig config = mapToBean(JsonParserFactory.getJsonParser().parseMap(configStr), payClass);

        Assert.notNull(config, "支付配置不能为空");

        // 验证参数
        config.validate(validator);
        return config;
    }

    @Override
    public void deleteChannel(Long id) {
        // 校验存在
        validateChannelExists(id);

        // 删除
        payChannelMapper.deleteById(id);
    }

    private PayChannel validateChannelExists(Long id) {
        Optional<PayChannel> channel = payChannelMapper.findById(id);
        if (channel.isEmpty()) {
            throw exception(CHANNEL_NOT_FOUND);
        }
        return channel.get();
    }

    @Override
    public PayChannelDto getChannel(Long id) {
        return AutoMapperProfile.INSTANCE.convert(payChannelMapper.findById(id).orElseThrow());
    }

    @Override
    public ListResultDto<PayChannelDto> getChannelListByAppIds(Collection<Long> appIds) {
        return new ListResultDto<>(AutoMapperProfile.INSTANCE.convertChannel(payChannelMapper.findByAppIdIn(appIds)));
    }

    @Override
    public PayChannelDto getChannelByAppIdAndCode(Long appId, String code) {
        return AutoMapperProfile.INSTANCE.convert(payChannelMapper.findByAppIdAndCode(appId, code));
    }

    @Override
    public PayChannelDto validPayChannel(Long id) {
        PayChannel channel = payChannelMapper.findById(id).orElseThrow();
        validPayChannel(channel);
        return AutoMapperProfile.INSTANCE.convert(channel);
    }

    @Override
    public PayChannelDto validPayChannel(Long appId, String code) {
        PayChannel channel = payChannelMapper.findByAppIdAndCode(appId, code);
        validPayChannel(channel);
        return AutoMapperProfile.INSTANCE.convert(channel);
    }

    private void validPayChannel(PayChannel channel) {
        if (channel == null) {
            throw exception(CHANNEL_NOT_FOUND);
        }
        if (Status.DISABLED.equals(channel.getStatus())) {
            throw exception(CHANNEL_IS_DISABLE);
        }
    }

    @Override
    public ListResultDto<PayChannelDto> getEnableChannelList(Long appId) {
        return new ListResultDto<>(AutoMapperProfile.INSTANCE.convertChannel(payChannelMapper.findByAppIdAndStatus(appId, Status.ENABLED)));
    }

    @Override
    public PayClient<?> getPayClient(Long id) {
        PayChannelDto channel = validPayChannel(id);
        return payClientFactory.createOrUpdatePayClient(id, channel.getCode(), channel.getConfigObject());
    }

}
